This is a race.
org.apache.commons.dbcp.datasources.InstanceKeyObjectFactory#removeInstance
method is not synchronized, so instanceMap field could be
accessed by multiple threads unsafely.
An exception expected
by running the test:
Exception in thread "Thread-0": java.util.ConcurrentModificationExceptionMore details about this bug are at DBCP-369 JIRA page.
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at org.apache.commons.dbcp.datasources.InstanceKeyObjectFactory.registerNewInstance(InstanceKeyObjectFactory.java:50)
at org.apache.commons.dbcp.datasources.Dbcp369$1.run(Dbcp369.java:58)
at java.lang.Thread.run(Thread.java:662)
Numbers in following code snippets present the global execution sequence.
org.apache.commons.dbcp.datasources.InstanceKeyObjectFactory:
t1 t2 synchronized static String registerNewInstance(InstanceKeyDataSource ds) {
int max = 0;
Iterator i = instanceMap.keySet().iterator();
while (i.hasNext()) {
1 Object obj = i.next();
...
}
...
}
static void removeInstance(String key){
3 instanceMap.remove(key);
}
java.util.HashMap:
t1 t2 final Entry<K,V> nextEntry() {
2 if (modCount != expectedModCount)
5 throw new ConcurrentModificationException();
...
}
final Entry<K,V> removeEntryForKey(Object key) {
...
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
4 modCount++;
size--;
...
a) Thread t1 calls registerNewInstance method and comes to 1 to
call next.
b) Thread t1 comes to 2 in HashMap
class. Now modCount and expectedModCount are equal.
c) Before t1 executes 2, context switched, and thread t2 calls remove
method at 3.
d) When t2 calls remove method, it comes
to removeEntryForKey method in HashMap class, and
executes 4.
e) Context switched, t1 now checks modCount
and expectedModCount at 2. They are not equal after step d).
f) T1 comes to 5 and throws exception.
This bug is reproduced under dbcp 1.2 and JDK 1.6.0_33.
Execute
the following scripts to run the test to reproduce the bug (assume the
location of the dbcp test project is dbcp_test_home).
Linux:
${dbcp_test_home}/scripts/369.sh [--loops arg]
Windows:
%dbcp_test_home%\scripts\369.bat [--loops arg]
The default argument values will be taken if not specified.For
example,
${dbcp_test_home}/scripts/369.sh
is
the same as
${dbcp_test_home}/scripts/369.sh --loops
10
| Option | Function | Default Value | Valid Values |
| --loops,-l | number of loops. | 10 | integer |